/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.transport.util;


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.Socket;
import java.util.Properties;

import cgl.narada.transport.http.HTTPTransportImpl;
import cgl.narada.transport.ssl.SSLTransport;
import cgl.narada.transport.ssl.SSLTransportJSSEImpl;
import cgl.narada.util.ByteUtilities;


/**
 * When NB program is running behind firewall or proxy server, the 
 * class can be used to probe the possibly allowed outward communication type.
 *
 * It probes for the connection possibility with various transport types at certain 
 * ports traditionally used in NB. It reporst the result on the console and
 * then writes a configuration file which will be used by NB client to look
 * for an ideal type of connection. The mechanism will work only when the remote
 * host has acceptors running.
 */
public class TransportProbe {
   
    String iub = "www.iub.edu";
    String cgl = "ryhn.ucs.indiana.edu";
    int bufLen = 1024;
    static String code = "sesame";
    /**
     * Test results -- default to false. Other than tcp80, all other tests will
     * take the CGL lab machine as the remote host; tcp80 will connect to a well
     * known website, for example, www.iub.edu.
     */
    public boolean tcp3045;
    public boolean tcp5045;
    public boolean udp3045;
    public boolean multicast4045;
    public boolean ssl8045;
    public boolean http9045;

    public boolean http80;
    public boolean udp80;
    /**
     * Test if a connection can be made to the remote host on certain port.
     * @param port is the destination port which can be connected to.
     */
    public boolean tcpProbe(String host, int port) {
	if (host == null) host = cgl;
	Socket sock = null;
	try {
	    sock = new Socket(host, port);
	    System.out.println("local port[tcp]="+sock.getLocalPort());
	    if (sock != null) {
		return true;
	    }
	    else return false;

	} catch (Exception e) { 
	    return false;
	} finally {
	    if (sock != null && !sock.isClosed()) {
		try {
		    sock.close();
		} catch (Exception e) { e.printStackTrace();}
	    }
	}  
	   
    }
    
    public boolean udpProbe(String host, int port) throws Exception {
	if (host == null) host = cgl;

	DatagramSocket dsock = null;
	java.util.Random ran = new java.util.Random();
	int srcPort = 0;
	while (true) {
	    try {
		srcPort = ran.nextInt(10000) + 30000;
		dsock = new DatagramSocket(srcPort);
		break;
	    } catch (java.net.BindException be) { continue;}
	}
	if (dsock == null) return false;

	System.out.println("local port[udp]="+srcPort);
	byte[] sesame = code.getBytes();
	byte[] replyBuf = new byte[bufLen];
	InetAddress inet = InetAddress.getByName(host);
	DatagramPacket recvP = new DatagramPacket(replyBuf, replyBuf.length);
	DatagramPacket sendP = new DatagramPacket(sesame, sesame.length, inet, port);
	/* Arbitrary number of tries to address the case that remote connection
	 * could've been allowed if the datagram packet was not lost on the road.
	 */
	int tries = 5;
	for(int i=0; i<tries; i++) { 
	    dsock.send(sendP);
        }
	for(int i=0; i<tries+30; i++) {
	    dsock.receive(recvP);
	    String reply = new String(recvP.getData(), 0, recvP.getLength());
	    System.out.println("reply="+reply+", ip="+ recvP.getAddress().toString()+", port="+recvP.getPort());
	    if (reply != null && reply.equals(code)) {
		System.out.println("true for "+ port);
		return true;
	    }
	}
	return false;                           //we need another thread to determine if it's false.

    }

    public boolean multicastProbe(int port) throws Exception {
	int mport = port;
	if (port == 0) mport = 4045;

	InetAddress group = InetAddress.getByName("224.224.224.224");
	if (group.isMulticastAddress()) {
	    System.out.println("address is multicast");
	}
	MulticastSocket s = new MulticastSocket(mport);
	s.joinGroup(group);
	int challenge = new java.util.Random().nextInt(1024*1024);
	byte[] msg = ByteUtilities.getBytes(challenge);
	DatagramPacket hi = new DatagramPacket(msg, msg.length, group, mport);
	int tries = 5;
	for(int i=0; i<tries; i++)
	    s.send(hi);
	byte[] buf = new byte[1000];
	DatagramPacket recvP = new DatagramPacket(buf, buf.length);
	for(int i=0; i<tries*3; i++) {
	    s.receive(recvP);
	    int reply = ByteUtilities.getInt(recvP.getData());
	    System.out.println("reply="+reply+", ip="+ recvP.getAddress().toString()+", port="+recvP.getPort()); 
            if (reply == challenge+1) return true;
	}
	s.leaveGroup(group);
	return false;
    }

    public boolean httpProbe(String host, int port) {
	if (host == null) host = cgl;
	HTTPTransportImpl transport = new HTTPTransportImpl();
	Properties prop = new Properties();
	prop.setProperty("portnum", ""+port);
	prop.setProperty("hostname", host);
	prop.setProperty("acceptor.http.port", ""+port);
	prop.setProperty("localhost", localHost());
	transport.setProperties(prop);

	try {
	    transport.connect();
	    byte[] sesame = code.getBytes();
	    transport.sendData(sesame);
	    String response = new String(transport.receiveData());
	    System.out.println("response="+response);
	    if (response.startsWith(code)) return true;
	} catch (Exception e) {
	    e.printStackTrace();
	    return false;
	}
	return false;
    }

    public boolean sslProbe(String host, int port, Properties prop) {
	if (host == null) host = cgl;

	SSLTransport transport = new SSLTransportJSSEImpl();
	prop.setProperty("portnum", ""+port);
	prop.setProperty("hostname", host);
	prop.setProperty("acceptor.ssl.port", ""+port);
	prop.setProperty("localhost", localHost());
	transport.setProperties(prop);

	try {
	    transport.connect();
	    byte[] sesame = code.getBytes();
	    transport.sendData(sesame);
	    String response = new String(transport.receiveData());
	    System.out.println("response="+response);
	    if (response.startsWith(code)) return true;
	} catch (Exception e) {
	    e.printStackTrace();
	    return false;
	}
	return false;	
    }

    public String localHost() {
	String host = null;
	try {
	    host = InetAddress.getLocalHost().getHostName();
	} catch (Exception e) { }
	return host;
    }

    private static String cmdInput(String prompt, BufferedReader br, PrintWriter pw) {
	String s = null;
	try {
	    pw.println(prompt);
	    s = br.readLine();
	} catch (Exception e) {
	    e.printStackTrace();
	}
	if (s != null && s.equals("")) s = null;
	return s;
	
    }


    public static void main(String[] args) throws Exception {
	System.out.println("Usage: java "+TransportProbe.class+" <host>");
	
	final TransportProbe probe = new TransportProbe();
	final String host = args[0];
	final String iub = probe.iub;
	final String cgl = probe.cgl;

	System.out.println("\nWe are collecting information in order to make SSL connection. If you\n"+
			   "don't know, just hit the enter key. Otherwise, please type correctly.\n");
	
	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)), true);
	String proxyHost = cmdInput("Please type proxy server's fully qualified name:", br, pw);
	String proxyPort = cmdInput("Plese type port number for the proxy server:", br, pw);
	String userName = cmdInput("Please type user name for proxy authentication:", br, pw);
	String password = cmdInput("Please type password for the user:", br, pw);
	String trustStore = cmdInput("If you know the trust store, please type:", br, pw);
	String trustStorePasswd = cmdInput("If you know the trust store password, please type:", br, pw);

	Properties sslProps = new Properties();
	if (proxyHost != null) sslProps.put("https.proxyHost", proxyHost);
	if (proxyPort != null) sslProps.put("https.proxyPort", proxyPort);
	if (userName != null) sslProps.put("username", userName);
	if (password != null) sslProps.put("password", password);
	if (trustStore != null) sslProps.put("initiator.trustStore", trustStore);
	if (trustStorePasswd != null) sslProps.put("initiator.trustStorePassword", trustStorePasswd);
	System.out.println("Done with SSL info collecting.");

	probe.tcp3045 = probe.tcpProbe(host, 3045);
	probe.tcp5045 = probe.tcpProbe(host, 5045);

	Thread d80 = new Thread("d80") {
		public void run() {
		    try {
			probe.udp80 = probe.udpProbe(host, 80);
		    } catch (Exception e) {
			e.printStackTrace();
		    }
		}
	    };
	d80.start();

	Thread d3045 = new Thread("d3045") {
		public void run() {
		    try {
			probe.udp3045 = probe.udpProbe(host, 3045);
		    } catch (Exception e) {
			e.printStackTrace();
		    }
		}
	    };
	d3045.start();

	Thread m4045 = new Thread("m4045") {
		public void run() {
		    try {
			probe.multicast4045 = probe.multicastProbe(4045);
		    } catch (Exception e) {
			e.printStackTrace();
		    }
		}
	    };
	m4045.start();

	Thread.sleep(10*1000);
	
	if (d80.isAlive()) {
	    probe.udp80 = false;
	    d80.interrupt();
	} 
	
	if (d3045.isAlive()) {
	    probe.udp3045 = false;
	    d3045.interrupt();
	}

	if (m4045.isAlive()) {
	    probe.multicast4045 = false;
	    m4045.interrupt();
	}

	probe.http9045 = probe.httpProbe(host, 9045);
	probe.http80 = probe.httpProbe(host, 80);

	probe.ssl8045 = probe.sslProbe(host, 8045, sslProps);

	System.out.println("\ntcp 3045             " + probe.tcp3045 +
			   "\ntcp 5045             " + probe.tcp5045 +
			   "\nudp 80               " + probe.udp80 +
			   "\nudp 3045             " + probe.udp3045 +
			   "\nmcast 4045           " + probe.multicast4045 +
			   "\nssl 8045             " + probe.ssl8045 +
			   "\nhttp 9045            " + probe.http9045 +
			   "\nhttp 80              " + probe.http80);

	String fname = "config/CommConfiguration.txt";
	File confFile = new File(fname);
	if (confFile.exists()) confFile.delete();
	PrintWriter conf = new PrintWriter(new FileOutputStream (new File(fname)), true);
	String head = 
	    "#################################################################"+
	    "\n##  F I R E W A L L/P R O X Y  T R A N S P O R T  C O N F I G  ##"+
	    "\n#################################################################\n";
	conf.println(head);
	if (!probe.tcp3045) conf.print("#");
	conf.println("TCPPort=3045\n");
	if (!probe.tcp3045) conf.print("#");
	conf.println("TCPPort=5045\n");
	if (!probe.udp3045) conf.print("#");
	conf.println("UDPPort=3045\n");
	if (!probe.multicast4045) conf.print("#");
	conf.println("MulticastPort=4045\n");

	if (!probe.ssl8045) conf.print("#");
	conf.println("SSLPort=8045");
	if (userName != null) conf.println("username="+userName);
	if (password != null) conf.println("password="+password);
	if (proxyHost != null) conf.println("proxyHost="+proxyHost);
	if (proxyPort != null) conf.println("proxyPort="+proxyPort);
	if (trustStore != null) conf.println("trustStore="+trustStore);
	if (trustStorePasswd != null && !trustStorePasswd.equals("")) 
	    conf.println("trustStorePassword="+trustStorePasswd+"\n");

	if (!probe.http9045) conf.print("#");
	conf.println("HTTPPort=9045\n");
	if (!probe.http80) conf.print("#");
	conf.println("HTTPPort=80\n");
	if (!probe.udp80) conf.print("#");
	conf.println("UDPPort=80\n");
	conf.close();
	
    }

}
